package com.xu.cloud.common.util;

import org.springframework.beans.BeanUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;

/**
 * 反射工具类
 *
 * @author xuguan
 * @since 2025/3/15
 */
public class ReflectUtil {

    public static <T> T instantiationClass(Class<?> classType, @Nullable Class<?>[] argTypes, Object[] args) {
        if (Objects.isNull(argTypes)) {
            argTypes = getArgTypes(args);
        }
        Constructor<?> ctor;
        try {
            ctor = ReflectionUtils.accessibleConstructor(classType, argTypes);
            return (T) BeanUtils.instantiateClass(ctor, args);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("constructor not found");
        }
    }

    public static <T> T instantiationClassForName(String className, @Nullable Class<?>[] argTypes, Object[] args) {
        Class<?> cls;
        try {
            cls = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("class not found");
        }
        return instantiationClass(cls, argTypes, args);
    }

    public static <T> T invokeGetField(Object target, String fieldName, Class<T> type) {
        try {
            final Field field = ReflectionUtils.findField(target.getClass(), fieldName, type);
            Assert.notNull(field, "field " + fieldName + " in class " + target.getClass().getName() + " not exist");
            ReflectionUtils.makeAccessible(field);
            return (T) ReflectionUtils.getField(field, target);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static void invokeSetField(Object target, String fieldName, Object value) {
        try {
            final Field field = ReflectionUtils.findField(target.getClass(), fieldName, value.getClass());
            Assert.notNull(field, "field " + fieldName + " in class " + target.getClass().getName() + " not exist");
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, target, value);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static <T> T invokeMethod(Object target, String methodName, @Nullable Class<?>[] argTypes, Object[] args) {
        if (Objects.isNull(argTypes)) {
            argTypes = getArgTypes(args);
        }
        try {
            final Method method = ReflectionUtils.findMethod(target.getClass(), methodName, argTypes);
            Assert.notNull(method, "method " + methodName + " in class " + target.getClass().getName() + " not exist");
            ReflectionUtils.makeAccessible(method);
            return (T) ReflectionUtils.invokeMethod(method, target, args);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    private static Class<?>[] getArgTypes(Object... args) {
        return Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
    }
}
